/*
 * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */


package com.sun.jmx.snmp.agent;

// java imports
//

import java.io.Serializable;
import java.util.Vector;
import java.util.Enumeration;

// jmx imports
//
import com.sun.jmx.snmp.SnmpOid;
import com.sun.jmx.snmp.SnmpVarBind;
import com.sun.jmx.snmp.SnmpStatusException;

/**
 * Represents a node in an SNMP MIB which is neither a group nor a variable.
 * This class defines a list of sub-nodes and the methods that allow to
 * manipulate the sub-nodes.
 * <P>
 * This class is used internally and by the class generated by
 * <CODE>mibgen</CODE>.
 * You should not need to use this class directly.
 *
 * <p><b>This API is a Sun Microsystems internal API  and is subject
 * to change without notice.</b></p>
 */

public class SnmpMibOid extends SnmpMibNode implements Serializable {

  private static final long serialVersionUID = 5012254771107446812L;

  /**
   * Default constructor.
   */
  public SnmpMibOid() {
  }

  // PUBLIC METHODS
  //---------------

  /**
   * Generic handling of the <CODE>get</CODE> operation.
   *
   * <p> This method should be overridden in subclasses.
   * <p>
   *
   * @param req The sub-request that must be handled by this node.
   * @param depth The depth reached in the OID tree.
   * @throws SnmpStatusException The default implementation (if not overridden) is to generate a
   * SnmpStatusException.
   */
  @Override
  public void get(SnmpMibSubRequest req, int depth)
      throws SnmpStatusException {
    for (Enumeration<SnmpVarBind> e = req.getElements(); e.hasMoreElements(); ) {
      SnmpVarBind var = e.nextElement();
      SnmpStatusException x =
          new SnmpStatusException(SnmpStatusException.noSuchObject);
      req.registerGetException(var, x);
    }
  }

  /**
   * Generic handling of the <CODE>set</CODE> operation.
   *
   * <p> This method should be overridden in subclasses.
   * <p>
   *
   * @param req The sub-request that must be handled by this node.
   * @param depth The depth reached in the OID tree.
   * @throws SnmpStatusException The default implementation (if not overridden) is to generate a
   * SnmpStatusException.
   */
  @Override
  public void set(SnmpMibSubRequest req, int depth)
      throws SnmpStatusException {
    for (Enumeration<SnmpVarBind> e = req.getElements(); e.hasMoreElements(); ) {
      SnmpVarBind var = e.nextElement();
      SnmpStatusException x =
          new SnmpStatusException(SnmpStatusException.noAccess);
      req.registerSetException(var, x);
    }
  }

  /**
   * Generic handling of the <CODE>check</CODE> operation.
   *
   * <p> This method should be overridden in subclasses.
   * <p>
   *
   * @param req The sub-request that must be handled by this node.
   * @param depth The depth reached in the OID tree.
   * @throws SnmpStatusException The default implementation (if not overridden) is to generate a
   * SnmpStatusException.
   */
  @Override
  public void check(SnmpMibSubRequest req, int depth)
      throws SnmpStatusException {
    for (Enumeration<SnmpVarBind> e = req.getElements(); e.hasMoreElements(); ) {
      SnmpVarBind var = e.nextElement();
      SnmpStatusException x =
          new SnmpStatusException(SnmpStatusException.noAccess);
      req.registerCheckException(var, x);
    }
  }


  // ---------------------------------------------------------------------
  //
  // Implements the method defined in SnmpMibNode.
  //
  // ---------------------------------------------------------------------
  //
  @Override
  void findHandlingNode(SnmpVarBind varbind,
      long[] oid, int depth,
      SnmpRequestTree handlers)
      throws SnmpStatusException {

    final int length = oid.length;
    SnmpMibNode node = null;

    if (handlers == null) {
      throw new SnmpStatusException(SnmpStatusException.snmpRspGenErr);
    }

    if (depth > length) {
      // Nothing is left... the oid is not valid
      throw new SnmpStatusException(SnmpStatusException.noSuchObject);
    } else if (depth == length) {
      // The oid is not complete...
      throw new SnmpStatusException(SnmpStatusException.noSuchInstance);
    } else {
      // Some children variable or subobject is being querried
      // getChild() will raise an exception if no child is found.
      //
      final SnmpMibNode child = getChild(oid[depth]);

      // XXXX zzzz : what about null children?
      //             (variables for nested groups)
      // if child==null, then we're dealing with a variable or
      // a table: we register this node.
      // This behaviour should be overriden in subclasses,
      // in particular in group meta classes: the group
      // meta classes that hold tables should take care
      // of forwarding this call to all the tables involved.
      //
      if (child == null) {
        handlers.add(this, depth, varbind);
      } else {
        child.findHandlingNode(varbind, oid, depth + 1, handlers);
      }
    }
  }

  // ---------------------------------------------------------------------
  //
  // Implements the method defined in SnmpMibNode.
  //
  // ---------------------------------------------------------------------
  //
  @Override
  long[] findNextHandlingNode(SnmpVarBind varbind,
      long[] oid, int pos, int depth,
      SnmpRequestTree handlers,
      AcmChecker checker)
      throws SnmpStatusException {

    final int length = oid.length;
    SnmpMibNode node = null;
    long[] result = null;
    if (handlers == null) {
      // This should be considered as a genErr, but we do not want to
      // abort the whole request, so we're going to throw
      // a noSuchObject...
      //
      throw new SnmpStatusException(SnmpStatusException.noSuchObject);
    }

    final Object data = handlers.getUserData();
    final int pduVersion = handlers.getRequestPduVersion();

    if (pos >= length) {
      long[] newOid = new long[1];
      newOid[0] = getNextVarId(-1, data, pduVersion);
      result = findNextHandlingNode(varbind, newOid, 0, depth, handlers,
          checker);
      return result;
    }

    // search the element specified in the oid
    //
    long[] newOid = new long[1];
    long index = oid[pos];

    while (true) {

      try {
        final SnmpMibNode child = getChild(index);
        // SnmpOid result = null;
        if (child == null) {
          // shouldn't happen
          throw new SnmpStatusException(SnmpStatusException.noSuchObject);
          // validateVarId(index);
          // handlers.add(this,varbind,depth);
          // result = new SnmpOid(0);
        } else {
          checker.add(depth, index);
          try {
            result = child.findNextHandlingNode(varbind, oid, pos + 1,
                depth + 1, handlers,
                checker);
          } finally {
            checker.remove(depth);
          }
        }

        // Build up the leaf OID
        result[depth] = index;
        return result;

      } catch (SnmpStatusException e) {
        // If there is no such element go one level up ...
        //
        index = getNextVarId(index, data, pduVersion);

        // There is no need to carry the original oid ...
        newOid[0] = index;
        pos = 1;
        oid = newOid;
      }
    }
  }


  /**
   * Computes the root OID of the MIB.
   */
  @Override
  public void getRootOid(Vector<Integer> result) {

    // If a node has several children, let assume that we are one step to
    // far in order to get the MIB root.
    //
    if (nbChildren != 1) {
      return;
    }

    result.addElement(varList[0]);

    // Now query our child.
    //
    children.firstElement().getRootOid(result);

  }

  /**
   * Registers a specific node in the tree.
   */
  public void registerNode(String oidString, SnmpMibNode node)
      throws IllegalAccessException {
    SnmpOid oid = new SnmpOid(oidString);
    registerNode(oid.longValue(), 0, node);
  }

  // PROTECTED METHODS
  //------------------

  /**
   * Registers a specific node in the tree.
   */
  void registerNode(long[] oid, int cursor, SnmpMibNode node)
      throws IllegalAccessException {

    if (cursor >= oid.length) {
      throw new IllegalAccessException();
    }

    // Check if the node is already defined
    //
    long var = oid[cursor];

    //System.out.println("entering registration for val="
    // + String.valueOf(var) + " position= " + cursor);

    int pos = retrieveIndex(var);
    if (pos == nbChildren) {
      nbChildren++;
      varList = new int[nbChildren];
      varList[0] = (int) var;
      pos = 0;
      if ((cursor + 1) == oid.length) {
        // That 's the end of the trip.
        // Do not forward the registration

        //System.out.println("End of trip for val="
        //      + String.valueOf(var) + " position= " + cursor);
        children.insertElementAt(node, pos);
        return;
      }

      //System.out.println("Create node for val="
      //       + String.valueOf(var) + " position= " + cursor);
      SnmpMibOid child = new SnmpMibOid();
      children.insertElementAt(child, pos);
      child.registerNode(oid, cursor + 1, node);
      return;
    }
    if (pos == -1) {
      // The node is not yet registered
      //
      int[] tmp = new int[nbChildren + 1];
      tmp[nbChildren] = (int) var;
      System.arraycopy(varList, 0, tmp, 0, nbChildren);
      varList = tmp;
      nbChildren++;
      SnmpMibNode.sort(varList);
      int newPos = retrieveIndex(var);
      varList[newPos] = (int) var;
      if ((cursor + 1) == oid.length) {
        // That 's the end of the trip.
        // Do not forward the registration

        //System.out.println("End of trip for val="
        //     + String.valueOf(var) + " position= " + cursor);
        children.insertElementAt(node, newPos);
        return;
      }
      SnmpMibOid child = new SnmpMibOid();
      // System.out.println("Create node for val=" +
      //     String.valueOf(var) + " position= " + cursor);
      children.insertElementAt(child, newPos);
      child.registerNode(oid, cursor + 1, node);
    } else {
      // The node is already registered
      //
      SnmpMibNode child = children.elementAt(pos);
      if ((cursor + 1) == oid.length) {
        //System.out.println("Node already registered val=" +
        //          String.valueOf(var) + " position= " + cursor);
        if (child == node) {
          return;
        }
        if (child != null && node != null) {
          // Now we're going to patch the tree the following way:
          //   if a subgroup has been registered before its father,
          //   we're going to replace the father OID node with
          //   the actual group-node and export the children from
          //   the temporary OID node to the actual group node.
          //

          if (node instanceof SnmpMibGroup) {
            // `node' is a group => replace `child' with `node'
            // export the child's subtree to `node'.
            //
            ((SnmpMibOid) child).exportChildren((SnmpMibOid) node);
            children.setElementAt(node, pos);
            return;

          } else if ((node instanceof SnmpMibOid) &&
              (child instanceof SnmpMibGroup)) {
            // `node' is a temporary node, and `child' is a
            //  group => keep child and export the node's
            //  subtree to `child'.
            //
            ((SnmpMibOid) node).exportChildren((SnmpMibOid) child);
            return;
          } else if (node instanceof SnmpMibOid) {
            // `node' and `child' are both temporary OID nodes
            // => replace `child' with `node' and export child's
            // subtree to `node'.
            //
            ((SnmpMibOid) child).exportChildren((SnmpMibOid) node);
            children.setElementAt(node, pos);
            return;
          }
        }
        children.setElementAt(node, pos);
      } else {
        if (child == null) {
          throw new IllegalAccessException();
        }
        ((SnmpMibOid) child).registerNode(oid, cursor + 1, node);
      }
    }
  }

  /**
   * Export this node's children to a brother node that will replace
   * this node in the OID tree.
   * This method is a patch that fixes the problem of registering
   * a subnode before its father node.
   **/
  void exportChildren(SnmpMibOid brother)
      throws IllegalAccessException {

    if (brother == null) {
      return;
    }
    final long[] oid = new long[1];
    for (int i = 0; i < nbChildren; i++) {
      final SnmpMibNode child = children.elementAt(i);
      if (child == null) {
        continue;
      }
      oid[0] = varList[i];
      brother.registerNode(oid, 0, child);
    }
  }

  // PRIVATE METHODS
  //----------------

  SnmpMibNode getChild(long id) throws SnmpStatusException {

    // first we need to retrieve the identifier in the list of children
    //
    final int pos = getInsertAt(id);
    if (pos >= nbChildren) {
      throw new SnmpStatusException(SnmpStatusException.noSuchObject);
    }

    if (varList[pos] != (int) id) {
      throw new SnmpStatusException(SnmpStatusException.noSuchObject);
    }

    // Access the node
    //
    SnmpMibNode child = null;
    try {
      child = children.elementAtNonSync(pos);
    } catch (ArrayIndexOutOfBoundsException e) {
      throw new SnmpStatusException(SnmpStatusException.noSuchObject);
    }
    if (child == null) {
      throw new SnmpStatusException(SnmpStatusException.noSuchInstance);
    }
    return child;
  }

  private int retrieveIndex(long val) {

    int low = 0;
    int cursor = (int) val;
    if (varList == null || varList.length < 1) {
      return nbChildren;
    }

    int max = varList.length - 1;
    int curr = low + (max - low) / 2;
    int elmt;
    while (low <= max) {
      elmt = varList[curr];
      if (cursor == elmt) {
        // We need to get the next index ...
        //
        return curr;
      }
      if (elmt < cursor) {
        low = curr + 1;
      } else {
        max = curr - 1;
      }
      curr = low + (max - low) / 2;
    }
    return -1;
  }

  private int getInsertAt(long val) {

    int low = 0;
    final int index = (int) val;
    if (varList == null) {
      return -1;
    }
    int max = varList.length - 1;
    int elmt;
    //final int[] v = varList;

    //if (index > a[max])
    //return max +1;

    int curr = low + (max - low) / 2;
    while (low <= max) {

      elmt = varList[curr];

      // never know ...we might find something ...
      //
      if (index == elmt) {
        return curr;
      }

      if (elmt < index) {
        low = curr + 1;
      } else {
        max = curr - 1;
      }
      curr = low + (max - low) / 2;
    }

    return curr;
  }

  // PRIVATE VARIABLES
  //------------------

  /**
   * Contains the list of sub nodes.
   */
  private NonSyncVector<SnmpMibNode> children = new NonSyncVector<>(1);

  /**
   * The number of sub nodes.
   */
  private int nbChildren = 0;


  // All the methods of the Vector class are synchronized.
  // Synchronization is a very expensive operation. In our case it is
  // not always required...
  //
  @SuppressWarnings("serial")
      // We will never serialize this
  class NonSyncVector<E> extends Vector<E> {

    public NonSyncVector(int size) {
      super(size);
    }

    final void addNonSyncElement(E obj) {
      ensureCapacity(elementCount + 1);
      elementData[elementCount++] = obj;
    }

    @SuppressWarnings("unchecked")  // cast to E
    final E elementAtNonSync(int index) {
      return (E) elementData[index];
    }

  }
}
